package MineGod;

import java.io.IOException;
import org.lwjgl.LWJGLException;
import org.lwjgl.Sys;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GL11;
import org.newdawn.slick.geom.*;
import org.newdawn.slick.Image;
import org.newdawn.slick.opengl.Texture;
import org.newdawn.slick.opengl.TextureLoader;
import org.newdawn.slick.util.ResourceLoader;
import java.util.ArrayList;



public class MineGod {
	static final boolean godMode = false;
	static final int windowWidth = (int) (Chunk.CHUNK_WIDTH*Block.WIDTH);
	static final int gameHeight = (int) (Chunk.CHUNK_HEIGHT*Block.WIDTH);
	static final int windowHeight = gameHeight + (int)GUIHUD.height;
	
	
	public static double worldPosition = 0;
	public static int chunkIndex = 0;
	public static ArrayList<Chunk> chunks = new ArrayList<Chunk>();
	
	public static ArrayList<ProjectileEntity> projectiles = new ArrayList<ProjectileEntity>();
	public static ArrayList<Entity> entities = new ArrayList<Entity>();
	public static ArrayList<Emitter> emitters = new ArrayList<Emitter>();

	public static ArrayList<Entity> delayedEntities = new ArrayList<Entity>();
	
	public static GUI gui;
	
	static int screenIndex = 0;  // 0 = menu, 1 = game
	static int menuFlag = 0;
	static int gameFlag = 0;
	static int nameFlag = 0;
	static int scoreFlag = 0;
	static boolean paused = false;
	static boolean gameFinished = false;
	
	
	
	public boolean escapePressed = false;
	
	/** time at last frame */
	long lastFrame;
	/** frames per second */
	int fps;
	/** last fps time */
	long lastFPS;
	
	static Player player;
	static EntityLavaWall lavaWall;
	
	//static Emitter emitter = new Emitter(300.0, 300.0, .5, .1, -.1, .3, .2,3000,500);
	//Emitter(double x, double y, double r, double sxmax, double sxmin, double symax, double symin,
	//        double lmax, double lmin){
	
	Image hMap;
	
	Texture paloTex;
		
	
	public void start() {
		GUIScoreMenu.updateLHS();
		try {
			Display.setDisplayMode(new DisplayMode(windowWidth, windowHeight));
			Display.create();
		} catch (LWJGLException e) {
			e.printStackTrace();
			System.exit(0);
		}
		
		initGL(); // init OpenGL

		loadGUITextures();
		loadTextures();	
		
		/* Menu Screen */
		getDelta(); // call once before loop to initialise lastFrame
		lastFPS = getTime(); // call before loop to initialise fps timer
		while (!Display.isCloseRequested() && !escapePressed) {
			Display.sync(100);
			updateFPS();
			if(screenIndex == 0){
				if(menuFlag == 0){
					gui = new GUIStartMenu();
					menuFlag = 1;
				}
				startMenuLoop();
			} else if (screenIndex == 1){
				if(gameFlag == 0){
					// Destroy any clicks
					gui = new GUIHUD();
					Mouse.destroy();
					try {
						Mouse.create();
					} catch (LWJGLException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}	
					player = new Player();
					lavaWall = new EntityLavaWall();
					
					if (!godMode){
						entities.add(lavaWall);
					}
					
					chunks.clear();
					chunks.add(new Chunk(chunks.size()));
					chunks.add(new Chunk(chunks.size(), getCurrChunk()));
					for (ResidualArrayElement rAE: chunks.get(1).rAEs){
						for (int[] locs: rAE.blockLocs){
							if (locs[0] < 0){
								chunks.get(0).blocks[(locs[0]+Chunk.CHUNK_WIDTH)%Chunk.CHUNK_WIDTH][locs[1]] = rAE.bGen.provideBlock();
							}
						}
					}
					chunks.add(new Chunk(chunks.size(), getNextChunk()));
					for (ResidualArrayElement rAE: chunks.get(2).rAEs){
						for (int[] locs: rAE.blockLocs){
							if (locs[0] < 0 && locs[1] != 0 /*The water goes at the top*/){
								chunks.get(1).blocks[(locs[0]+Chunk.CHUNK_WIDTH)%Chunk.CHUNK_WIDTH][locs[1]] = rAE.bGen.provideBlock();
							}
						}
					}
					gameFlag = 1;
					gui.refreshGUI(this);
					
				}
				gameLoop();
			} else if (screenIndex == 2){
				if (GUIScoreMenu.playerScore>GUIScoreMenu.lowestHighScore)
				{
					if (nameFlag == 0)
					{
						gui = new GUINameEntry();
						nameFlag = 1;
					}
					nameEnterLoop();
				}
				else{
					GUIScoreMenu.playerName = "";
					screenIndex = 3;
				}
			} else if (screenIndex == 3){
				if(scoreFlag == 0){
					gui = new GUIScoreMenu();
					scoreFlag = 1;
					gameFinished = false;
				}
				startMenuLoop();
			}
			
		}
		
		Display.destroy();
	}
	
	
	public static void endGame(){
		screenIndex = 2;
		scoreFlag = 0;
		gameFlag = 0;
		gameFinished = true;
		
		//update ScoreGUIScreent thing
		GUIScoreMenu.playerScore = (int)worldPosition;
		
		//erase chunks
		chunks.clear();
		worldPosition = 0;
		chunkIndex = 0;		
		projectiles.clear();
		entities.clear();
		delayedEntities.clear();
		emitters.clear();
		
		player = null;
	}
	
	
	
	public void startMenuLoop(){
		int delta = getDelta();
		updateMenu(delta);
		renderMenuGL();
		
		Display.update();
		//Display.sync(100); // cap fps to 100fps
	}
	
	public void nameEnterLoop(){
		//int delta = getDelta();
		pollInputNameEntry();//updateMenu(delta);
		renderMenuGL();
		
		Display.update();
		//Display.sync(100); // cap fps to 100fps
	}
	
	
	public void gameLoop(){
		int delta = getDelta();
		
		if(delta < 50){
			update(delta);
			renderGL();	
			if(player.hp <= 0){
				MineGod.endGame();
			}
			Display.update();
			//Display.sync(100); // cap fps to 60fps
		}
	}
	
	
	/** 
	 * Calculate how many milliseconds have passed 
	 * since last frame.
	 * 
	 * @return milliseconds passed since last frame 
	 */
	public int getDelta() {
	    long time = getTime();
	    int delta = (int) (time - lastFrame);
	    lastFrame = time;
 
	    return delta;
	}
 
	/**
	 * Get the accurate system time
	 * 
	 * @return The system time in milliseconds
	 */
	public long getTime() {
	    return (Sys.getTime() * 1000) / Sys.getTimerResolution();
	}
 
	/**
	 * Calculate the FPS and set it in the title bar
	 */
	public void updateFPS() {
		if (getTime() - lastFPS > 1000) {
			Display.setTitle("FPS: " + fps);
			fps = 0;
			lastFPS += 1000;
		}
		fps++;
	}
	
	
	
	public void initGL() {
		GL11.glMatrixMode(GL11.GL_PROJECTION);
		GL11.glEnable(GL11.GL_DEPTH_TEST);
		GL11.glEnable(GL11.GL_BLEND);
		GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
		//GL11.glEnable(GL11.GL_LIGHTING);
		
		GL11.glLoadIdentity();
		GL11.glOrtho(0, windowWidth, windowHeight, 0, 30, -30);
		GL11.glMatrixMode(GL11.GL_MODELVIEW);
		GL11.glEnable(GL11.GL_TEXTURE_2D);  
	}
	
	
	public void renderGL() {
		// Clear The Screen And The Depth Buffer
		GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
		// R,G,B,A Set The Color To Blue One Time Only
		//GL11.glColor3f(0.5f, 0.5f, 1.0f);
		GL11.glPushMatrix();
		GL11.glColor3d(1.0, 1.0, 1.0);  // THIS IS REALLY NOT NEEDED USUALLY
			/* THESE SHOULD BE RENDERED FROM BACK TO FRONT TO DISPLAY TRANSPARENCY CORRECTLY!! */
			ArrayList<Entity> oneZIndex = new ArrayList<Entity>();
			ArrayList<Entity> zeroZIndex = new ArrayList<Entity>();
			ArrayList<Entity> negHalfZIndex = new ArrayList<Entity>();
			ArrayList<Entity> neg2ZIndex = new ArrayList<Entity>();
			
			
			for(Entity e: entities){
				if (e.zIndex == 1){
					oneZIndex.add(e);
				} else if (e.zIndex == 0){
					zeroZIndex.add(e);
				} else if (e.zIndex == -.5){
					negHalfZIndex.add(e);
				} else if (e.zIndex == -2){
					neg2ZIndex.add(e);
				}
				
			}
			
			// zIndex = 1
			for(Entity e: oneZIndex){
				e.render(worldPosition);
			}
			
			// zIndex = 0
			for(Entity e: zeroZIndex){
				e.render(worldPosition);
			}
			
			// zIndex = 0
			if (getPrevChunk() != null){
				getPrevChunk().renderChunk(worldPosition);
			}
			getCurrChunk().renderChunk(worldPosition);
			getNextChunk().renderChunk(worldPosition);
			
			// zIndex = -0.5
			for(Entity e: negHalfZIndex){
				e.render(worldPosition);
			}
			
			//zIndex = -1
			player.render();
			
			// zIndex = -2
			for(Entity e: neg2ZIndex){
				e.render(worldPosition);
			}
			
			// zIndex = -3
			for(ProjectileEntity entity: projectiles){
				entity.render(worldPosition);
			}
			
			for (Emitter emitter: emitters){
				emitter.render();
			}
			
			gui.render();
			
		GL11.glPopMatrix();
	}
 
	
	public void update(int delta) {
		pollInput(); 
		if (Mouse.getX()+worldPosition<player.xPos)
		{
			player.leftFacing = true;
		}
		else{
			player.leftFacing = false;
		}
		if (screenIndex == 1 && paused == false){
			/*
			 * Player Logic
			 */
			player.doLogic(delta, getPrevChunk(), getCurrChunk(), getNextChunk());
			
			ArrayList<Entity> removes2 = new ArrayList<Entity>();
			for (Entity entity: entities){
				if (entity.x < lavaWall.x){
					entity.dead = true;
					removes2.add(entity);
					continue;
				}
				entity.doLogic(delta, getPrevChunk(), getCurrChunk(), getNextChunk());
				if(Utils.intersects(player.getNextMask((float)player.xSpeed, (float)player.ySpeed), entity.mask)){
					entity.interactWithPlayerOnCollision(player);
				}
				
				if(entity.dead == true){
					removes2.add(entity);
				}
				
			}
			
			
			for (Entity e: removes2){
				entities.remove(e);
			}
			for (Entity e: delayedEntities){
				entities.add(e);
			}
			delayedEntities = new ArrayList<Entity>();
			
			
			
			/*
			 * Projectile Logic
			 */
			ArrayList<ProjectileEntity> removes = new ArrayList<ProjectileEntity>();
			for(ProjectileEntity entity: projectiles){
				entity.doLogic(delta, getPrevChunk(), getCurrChunk(), getNextChunk());
				if (entity.age > entity.maxAge || entity.dead){
					removes.add(entity);
				}
			}
			for(ProjectileEntity r: removes){
				projectiles.remove(r);
			}
			ProjectileEntitySmall.lastShot += delta;
			ProjectileEntityBig.lastShot += delta;
			ProjectileEntityDestroy.lastShot += delta;
			
			/*
			 * Emitter logic
			 */
			ArrayList<Emitter> r = new ArrayList<Emitter>();
			for (Emitter emitter: emitters){
				emitter.step(delta);
				if (emitter.alive == false){
					r.add(emitter);
				}
			}
			for (Emitter emitter: r){
				emitters.remove(emitter);
			}
			
			
			
			
		}
		updateChunks();
	}
	
	
	public void updateChunks(){
		int prevIndex = chunkIndex;
		chunkIndex = (int)(player.xPos / Chunk.CHUNK_PIXEL_WIDTH);
		if (prevIndex < chunkIndex && /*Also check if we are near the end of chunks, otherwise blending screws up*/ chunkIndex == chunks.size()-2){
			chunks.add(generateNextChunk());
		}
	}
	
	public Chunk getPrevChunk(){
		if(chunkIndex > 0){
			return chunks.get(chunkIndex-1);
		} else {
			return null;
		}
	}
	
	public Chunk getCurrChunk(){
		return chunks.get(chunkIndex);
	}
	
	public Chunk getNextChunk(){
		return chunks.get(chunkIndex+1);
	}
	
	public Chunk getUberNextChunk(){
		return chunks.get(chunkIndex+2);
	}
	
	public Chunk generateNextChunk(){
		Chunk temp = new Chunk(chunks.size(), getNextChunk());
		Chunk prevChunk = getNextChunk();
		for (ResidualArrayElement rAE: temp.rAEs){
			for (int[] locs: rAE.blockLocs){
				if (locs[0] < 0 && locs[1] != 0 /*The water goes at the top*/){
					prevChunk.blocks[(locs[0]+Chunk.CHUNK_WIDTH)%Chunk.CHUNK_WIDTH][locs[1]] = rAE.bGen.provideBlock();
				}
			}
		}
		return temp;
	}
	
	
	
	public void pollInput() { 
		
		if (paused == false){
			if (Mouse.isButtonDown(0)) {
				int mouseX = Mouse.getX();
				int mouseY = Mouse.getY();
				shootProjectile(mouseX, mouseY);
				//entities.add(new EntityMobBat(mouseX,windowHeight-mouseY));
	        }
	        while (Keyboard.next()) {
	        	if (Keyboard.getEventKeyState()) {
	        		if (Keyboard.getEventKey() == Keyboard.KEY_S) {
	        			player.climbDown(1);
	        		} else if (Keyboard.getEventKey() == Keyboard.KEY_A) {
	        			player.walkLeft(-1);
	        		} else if (Keyboard.getEventKey() == Keyboard.KEY_D) {
	        			player.walkRight(1);
	        		} else if (Keyboard.getEventKey() == Keyboard.KEY_W){
	        			player.climbUp(-1);
	        		} else if (Keyboard.getEventKey() == Keyboard.KEY_P){
	        			if(paused == false){
	        				paused = true;
	        				break;
	        			}
	        		} else if (Keyboard.getEventKey() == Keyboard.KEY_SPACE){
	        			player.weaponSelectedIndex = player.getNextUsableInventorySlot();
	        			gui.updateLabels(this);
	        		}
	        	} else {
	        		if (Keyboard.getEventKey() == Keyboard.KEY_S) {
	        			player.climbDown(0);
	        		} else if (Keyboard.getEventKey() == Keyboard.KEY_A) {
	        			player.walkLeft(0);
	        		} else if (Keyboard.getEventKey() == Keyboard.KEY_D) {
	        			player.walkRight(0);
	        		} else if (Keyboard.getEventKey() == Keyboard.KEY_W){
	        			player.climbUp(0);
	        		} else if (Keyboard.getEventKey() == Keyboard.KEY_ESCAPE){
	        			escapePressed = true;
	        		}
	        	}
	        }
		} else {
			 while (Keyboard.next()) {
		        	if (Keyboard.getEventKeyState()) {
		        		if (Keyboard.getEventKey() == Keyboard.KEY_P){
		        			paused = false;
		        		}
		        	}
		     	}
			 
			}  		
		}
	
	public void pollInputNameEntry(){
		while(Keyboard.next()){
			if(Keyboard.getEventKeyState()){
				if (!(Keyboard.getEventKey() == Keyboard.KEY_BACK || Keyboard.getEventKey() == Keyboard.KEY_RETURN) && (((int)Keyboard.getEventCharacter()>=65 && (int)Keyboard.getEventCharacter()<=90) || ((int)Keyboard.getEventCharacter()>=97 && (int)Keyboard.getEventCharacter()<=122)))
				{
					GUILabelNameTyping.addLetter(Keyboard.getEventCharacter());
				} else if (Keyboard.getEventKey() == Keyboard.KEY_BACK){
					GUILabelNameTyping.removeLetter();
				} else if (Keyboard.getEventKey() == Keyboard.KEY_RETURN){
					MineGod.screenIndex = 3;
					MineGod.menuFlag = 0;
					MineGod.gameFlag = 0;
					MineGod.nameFlag = 0;
					GUIScoreMenu.playerName = GUILabelNameTyping.text;
				}
			}
		}
		if (Mouse.isButtonDown(0)) {
			int mouseX = Mouse.getX();
			int mouseY = windowHeight - Mouse.getY();
			gui.handleMouseDown(this, mouseX, mouseY);
        }
	}
	
	private void loadTextures(){
		try {
			//player.texture = TextureLoader.getTexture("PNG", ResourceLoader.getResourceAsStream("res/player.png"));
			BlockDirt.loadTexture();
			BlockStone.loadTexture();
			BlockSand.loadTexture();
			BlockWater.loadTexture();
			BlockLava.loadTexture();
			BlockHardStone.loadTexture();
			ItemEntityGunpowder.gunpowderTex = TextureLoader.getTexture("PNG", ResourceLoader.getResourceAsStream("res/gunpowder.png"));
			ItemEntityDiamond.diamondTex = TextureLoader.getTexture("PNG", ResourceLoader.getResourceAsStream("res/diamond.png"));
			
			EntityMobBat.batTex = TextureLoader.getTexture("PNG", ResourceLoader.getResourceAsStream("res/mobBat_0.png"));
			
			ProjectileEntitySmall.projectileTex = TextureLoader.getTexture("PNG", ResourceLoader.getResourceAsStream("res/regShot.png"));
			ProjectileEntityBig.projectileTex = TextureLoader.getTexture("PNG", ResourceLoader.getResourceAsStream("res/BoomShot.png"));
			ProjectileEntityDestroy.projectileTex = TextureLoader.getTexture("PNG", ResourceLoader.getResourceAsStream("res/destroyShot.png"));
			
		}catch (IOException e){
			e.printStackTrace();
			System.exit(-1);
		}
		
	}
	
	
	private void loadGUITextures(){
		GUIButtonStart.loadTexture();		
		GUILabelProjectileSmall.loadTexture();
		GUILabelProjectileBig.loadTexture();
		GUILabelHealthBar.loadTexture();
		GUILabelProjectileDestroy.loadTexture();
		GUILabelInventory.loadTexture();
		GUIButtonStartMenu.loadTexture();
		GUILabelScore.loadTexture();
		GUILabelNameTyping.loadTexture();
		GUIButtonNameEntry.loadTexture();
		GUIStartMenu.loadTexture();
		GUIButtonGoToHighScores.loadTexture();
		GUIScoreMenu.loadTexture();
		GUILabelYourScore.loadTexture();
	}
	
	
	private void updateMenu(int delta){
		pollMenuInput(); 
	}
	
	
	public void renderMenuGL() {
		// Clear The Screen And The Depth Buffer
		GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
		// R,G,B,A Set The Color To Blue One Time Only
		//GL11.glColor3f(0.5f, 0.5f, 1.0f);
		GL11.glPushMatrix();
		GL11.glColor3d(1.0, 1.0, 1.0);  // THIS IS REALLY NOT NEEDED USUALLY
		gui.render();
		
		GL11.glPopMatrix();
	}
	
	
	public void pollMenuInput(){
		if (Mouse.isButtonDown(0)) {
			int mouseX = Mouse.getX();
			int mouseY = windowHeight - Mouse.getY();
			gui.handleMouseDown(this, mouseX, mouseY);
        }
	}
	
	

	public void shootProjectile(int mouseX, int mouseY){
		switch(player.weaponSelectedIndex){
			case 0: // ProjectileEntitySmall
				if(ProjectileEntitySmall.lastShot > ProjectileEntitySmall.shotSpacing){				
					projectiles.add(new ProjectileEntitySmall(player.xPos, player.yPos).shoot(mouseX, windowHeight-mouseY, player.getScreenX(), player.yPos, player.xSpeed, player.ySpeed));
				}
				break;
			case 1:
				if(ProjectileEntityBig.lastShot > ProjectileEntityBig.shotSpacing){				
					projectiles.add(new ProjectileEntityBig(player.xPos, player.yPos).shoot(mouseX, windowHeight-mouseY, player.getScreenX(), player.yPos, player.xSpeed, player.ySpeed));
					ProjectileEntityBig.lastShot = 0;
					player.inventory[0] = Math.max(player.inventory[0]-1, 0);
					if(!player.hasAmpleResourcesForWeapon(1)){
						player.weaponSelectedIndex = 0;
						ProjectileEntitySmall.lastShot = -ProjectileEntityBig.shotSpacing;
						gui.updateLabels(this);
					}
				}
				break;
			case 2:
				if(ProjectileEntityDestroy.lastShot > ProjectileEntityDestroy.shotSpacing){				
					projectiles.add(new ProjectileEntityDestroy(player.xPos, player.yPos).shoot(mouseX, windowHeight-mouseY, player.getScreenX(), player.yPos, player.xSpeed, player.ySpeed));
					ProjectileEntityDestroy.lastShot = 0;
					player.inventory[1] = Math.max(player.inventory[1]-1, 0);
					if(!player.hasAmpleResourcesForWeapon(2)){
						player.weaponSelectedIndex = 0;
						ProjectileEntitySmall.lastShot = -ProjectileEntityDestroy.shotSpacing;
						gui.updateLabels(this);
					}
				}
		}
	}
	
	
	
	
	public static void main(String[] argv) {
		//System.get
		System.setProperty("org.lwjgl.librarypath",System.getProperty("user.dir") + "/libs/lwjgl-2.8.3/native");
		MineGod game = new MineGod();
		game.start();
	}
}
